#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include <mutex>
#include "util.hpp"
//#include "log.hpp"

namespace ns_index
{
	struct DocInfo
	{
		std::string title; // 文档标题
		std::string content; // 去标签之后的文档内容
		std::string url; // 文档 url
		uint64_t doc_id; // 文档的 id
	};

	struct InvertedElem
	{ 
		uint64_t doc_id;
		std::string word;
		int weight;
	};

	// 倒排拉链：拿着关键字，一拿就拿出来一批 InvertedElem
	typedef std::vector<InvertedElem> InvertedList;

	class Index
	{
	private:
		// 正排索引的数据结构用数组实现，数组的下标天然是文档的 id, 把文档内容写到数组中后，数组[下标] 的形式 O(1) 就可以找到文档内容
		std::vector<DocInfo> forward_index; // 正排索引
		// 倒排索引一定是一个关键字和一组（个） InvertedElem 对应 -- 关键字和倒排拉链对应 -- 存放关键字和倒排拉链的映射
		std::unordered_map<std::string, InvertedList> inverted_index; 
	private:
		Index() {} 
		Index(const Index&) = delete;
		Index& operator=(const Index&) = delete;
		static Index* instance;
		static std::mutex mtx;
	public:
		~Index() {}
	public:
		// 获取单例的函数
		static Index* GetInstance()
		{
			if (nullptr == instance)
			{
				mtx.lock();
				if (nullptr == instance)
				{
					instance = new Index();
				}
				mtx.unlock();
			}
			return instance;
		}

		// 根据 doc_id 找到文档内容
		DocInfo* GetForwardIndex(uint64_t doc_id)
		{
			// doc_id 是被当成数组下标使用的，下标不可能等于 size，只会是 size - 1
			if (doc_id >= forward_index.size())
			{
				std::cerr << "doc_id out range, error!" << std::endl;
				return nullptr;
			}
			return &forward_index[doc_id];
		}

		// 根据关键字 string，获取倒排拉链
		InvertedList* GetInveredList(const std::string& word)
		{
			auto iter = inverted_index.find(word);
			if (iter == inverted_index.end())
			{
				std::cerr << word << " has no InvertedList" << std::endl;
				return nullptr;
			}
			return &(iter->second);
		}

		// 构建索引 根据去标签，格式化之后的文档，构建正排和倒排索引 data/cln_html/cln.txt
		bool BulidIndex(const std::string& input)
		{
			std::ifstream in(input, std::ios::in | std::ios::binary);
			if (!in.is_open())
			{
				std::cerr << "sorry, " << input << " open error" << std::endl;
				return false;
			}

			std::string line;
			int count = 0; // for debug 计数器
			while (std::getline(in, line))
			{
				DocInfo* doc = BuildForwardIndex(line);
				if (doc == nullptr)
				{
					// 失败了，就继续 continue，这行文档就不要了
					std::cerr << "bulid: " << line << " error" << std::endl; // for debug
					continue;
				}

				BulidInvertedIndex(*doc);
				

				count++;
				if (count % 50 == 0)
				{
					std::cout << "当前已经建立的索引文档" << count << std::endl;
					//LOG(NORMAL, "当前的已经建立的索引文档: " + std  ::to_string(count));
				}
			}
			return true;
		}
		private:
			DocInfo* BuildForwardIndex(const std::string& line)
			{
				// 1. 解析 line，字符串切分
				// line 里面有三部分 title content url
				std::vector<std::string> results; // 里面放切分后的结果
				const std::string sep = "\3";
				ns_util::StringUtil::Split(line, &results, sep);
				if (results.size() != 3)
				{
					// 解析 line 失败
					return nullptr;
				}

				// 2. 字符穿填充到 DocInfo
				DocInfo doc;
				doc.title = results[0];
				doc.content = results[1];
				doc.url = results[2];
				doc.doc_id = forward_index.size(); // 即将插入的 doc_id 的值，是当前 forward_index 的 size，
												   // 因为它实际大小为 size - 1，插入后就是 size
												   // 先进行 doc_id 的保存，再插入，相当于提前给它设定了插入后在 vector 中所处的下标							
				// 3. 插入到正排索引的 vector
				forward_index.push_back(std::move(doc)); // doc 比较大，move 调用移动版本提高效率

				// 返回最新的 docinfo
				return &(forward_index.back()); 
			}

			bool BulidInvertedIndex(const DocInfo& doc)
			{
				// 关键字 -- 倒排拉链 --> 映射
				struct word_cnt // 词频统计
				{
					int title_cnt;
					int content_cnt;
					word_cnt():title_cnt(0), content_cnt(0){}
				};
				std::unordered_map<std::string, word_cnt> word_map; // 用来暂存词频的映射表

				// 标题分词
				std::vector<std::string> title_words;
				ns_util::JiebaUtil::CutString(doc.title, &title_words);

				// 对标题词频统计
				for (std::string s : title_words)
				{
					boost::to_lower(s);
					word_map[s].title_cnt++; // 存在就直接获取，不存在就新建
				}
				
				// 内容分词
				std::vector<std::string> content_words;
				ns_util::JiebaUtil::CutString(doc.content, &content_words);

				// 对内容词频统计
				for (std::string s : content_words)
				{
					boost::to_lower(s);
					word_map[s].content_cnt++; // 存在就直接获取，不存在就新建
				}

				for (auto &word_pair : word_map)
				{
					InvertedElem item;
					item.doc_id = doc.doc_id;
					item.word = word_pair.first;
					// 标题当中出现,权重更高.假设在标题出现1次权重为10,在内容出现1次权重为1
					item.weight = 10 * word_pair.second.title_cnt + word_pair.second.content_cnt; // 相关性
					InvertedList& inverted_list = inverted_index[word_pair.first];
					inverted_list.push_back(std::move(item));
				}

				return true;
			}
	};
	Index* Index::instance = nullptr;
	std::mutex Index::mtx;
}